use std::str::FromStr;

#[derive(Debug)]
pub enum Network {
    Mainnet,
    Testnet,
}

pub trait BlockchainConfig {
    const NAME: &'static str;
    // client url
    const MAINNET_RPC: &'static str;
    const TESTNET_RPC: &'static str;
    // api url
    const MAINNET_CLIENT: &'static str;
    const TESTNET_CLIENT: &'static str;
}

impl BlockchainConfig for Binance {
    const NAME: &'static str = "BSC";
    const MAINNET_RPC: &'static str = "https://api.bscscan.com";
    const TESTNET_RPC: &'static str = "https://api-testnet.bscscan.com";
    const MAINNET_CLIENT: &'static str = "https://bsc-dataseed.bnbchain.org";
    const TESTNET_CLIENT: &'static str = "https://data-seed-prebsc-1-s2.bnbchain.org:8545";
}

impl BlockchainConfig for Solana {
    const NAME: &'static str = "SOL";
    const MAINNET_RPC: &'static str = "wss://api.mainnet-beta.solana.com";
    const TESTNET_RPC: &'static str = "wss://api.devnet.solana.com";
    const MAINNET_CLIENT: &'static str = "https://api.mainnet-beta.solana.com";
    const TESTNET_CLIENT: &'static str = "https://api.devnet.solana.com";
}

impl BlockchainConfig for Tron {
    const NAME: &'static str = "TRON";
    const MAINNET_RPC: &'static str = "grpc.trongrid.io:50051";
    const TESTNET_RPC: &'static str = "grpc.nile.trongrid.io:50051";
    const MAINNET_CLIENT: &'static str = "https://api.nileex.io/";
    const TESTNET_CLIENT: &'static str = "https://nile.trongrid.io/";
}

#[derive(Debug)]
pub enum BlockchainError {
    UnknownBlockchainName(String),
}

#[derive(Debug)]
pub struct ChainInfo {
    name: &'static str,
    network: Network,
    pub client_url: &'static str,
    pub rpc_url: &'static str,
}

impl ChainInfo {
    fn new<T: BlockchainConfig>(network: Network) -> Self {
        let (client_url, rpc_url) = match network {
            Network::Mainnet => (T::MAINNET_CLIENT, T::MAINNET_RPC),
            Network::Testnet => (T::TESTNET_CLIENT, T::TESTNET_RPC),
        };
        Self {
            name: T::NAME,
            network,
            client_url,
            rpc_url,
        }
    }

    pub fn from_type(
        blockchain_type: BlockchainType,
        network: Network,
    ) -> Result<Self, BlockchainError> {
        match blockchain_type {
            BlockchainType::Binance => Ok(Self::new::<Binance>(network)),
            BlockchainType::Solana => Ok(Self::new::<Solana>(network)),
            BlockchainType::Tron => Ok(Self::new::<Tron>(network)),
        }
    }
}

#[derive(Debug)]
pub struct Binance;

#[derive(Debug)]
pub struct Solana;

#[derive(Debug)]
pub struct Tron;

#[derive(Debug)]
pub enum BlockchainType {
    Binance,
    Solana,
    Tron,
}

impl FromStr for BlockchainType {
    type Err = BlockchainError;
    fn from_str(name: &str) -> Result<Self, Self::Err> {
        match name {
            "BSC" => Ok(BlockchainType::Binance),
            "SOL" => Ok(BlockchainType::Solana),
            "TRON" => Ok(BlockchainType::Tron),
            _ => Err(BlockchainError::UnknownBlockchainName(name.to_string())),
        }
    }
}

#[derive(Debug)]
pub enum EnvError {
    UnknownEnvName(String),
}

pub enum EnvType {
    DEV,
    TEST,
    PROD,
}

impl FromStr for EnvType {
    type Err = EnvError;
    fn from_str(name: &str) -> Result<Self, Self::Err> {
        match name {
            "DEV" => Ok(EnvType::DEV),
            "TEST" => Ok(EnvType::TEST),
            "PROD" => Ok(EnvType::PROD),
            _ => Err(EnvError::UnknownEnvName(name.to_string())),
        }
    }
}

pub const ENV: &str = "TEST";

pub fn is_empty_json(data: &str) -> bool{
    let data_value: serde_json::Value = serde_json::from_str(data).unwrap();
    matches!(data_value, serde_json::Value::Object(map) if map.is_empty())
}